﻿using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace Ecobee.Net
{
	/// <summary>
	/// 
	/// </summary>
	public class Access : ApiObject
	{
		/// <summary>
		/// This event is fired whenever the Access object is updated. This allows
		/// the owner of this object to updated the stored version to ensure
		/// the most recent Access tokens are saved.
		/// </summary>
		public event EventHandler AccessTokenChanged = null;

		private Token _accessToken = new Token(Token.Kind.Authorization);
		private string _tokenType = string.Empty;
		private Token _refreshToken = new Token(Token.Kind.Authorization);
		private TimeSpan _lifetime = TimeSpan.Zero;
		private AuthorizationScope _responseScope = AuthorizationScope.None;
		private DateTime _timeStamp = DateTime.MinValue;

		/// <summary>
		/// 
		/// </summary>
		public Access()
		{
		}

		/// <summary>
		/// 
		/// </summary>
		public Token AccessToken
		{
			get
			{
				return _accessToken;
			}
			internal set
			{
				_accessToken = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public string TokenType
		{
			get
			{
				return _tokenType;
			}
			set
			{
				_tokenType = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public TimeSpan Lifetime
		{
			get
			{
				return _lifetime;
			}
			set
			{
				_lifetime = value;
			}
		}

		/// <summary>
		/// The amount of time until this authorization expires.
		/// </summary>
		public TimeSpan ExpiresIn
		{
			get
			{
				return DateTime.Now.Subtract(this.ExpiresAt);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public Token RefreshToken
		{
			get
			{
				return _refreshToken;
			}
			internal set
			{
				_refreshToken = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public AuthorizationScope ResponseScope
		{
			get
			{
				return _responseScope;
			}
			set
			{
				_responseScope = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public DateTime TimeStamp
		{
			get
			{
				return _timeStamp;
			}
			internal set
			{
				_timeStamp = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public DateTime ExpiresAt
		{
			get
			{
				return this.TimeStamp.Add(this.Lifetime);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public async Task<bool> IsValid()
		{
			bool returnValue = false;

			// ***
			// *** Refresh the token within one minute of
			// *** expiration.
			// ***
			if (this.AccessToken != null && !string.IsNullOrWhiteSpace(this.AccessToken.Identifier))
			{
				returnValue = await this.Refresh();
			}

			return returnValue;
		}

		/// <summary>
		/// 
		/// </summary>
		public async Task<bool> Refresh()
		{
			bool returnValue = false;

			ApiResult response = await Api.Http.Post(Api.KnownUri.RefreshToken(this.RefreshToken), null);

			if (response.Successful)
			{
				// ***
				// *** Convert this from the json object
				// ***
				this.FromDataObject(response.DataObject);

				// ***
				// *** Fire the changed event for the access token
				// ***
				this.FireAccessTokenChangedEvent();

				// ***
				// *** Return true
				// ***
				returnValue = true;
			}

			return returnValue;
		}

		/// <summary>
		/// Reset this instance to default values.
		/// </summary>
		public void Reset()
		{
			this.AccessToken = null;
			this.RefreshToken = null;
			this.TokenType = string.Empty;
			this.Lifetime = TimeSpan.Zero;
			this.ResponseScope = AuthorizationScope.None;
			this.TimeStamp = DateTime.MinValue;

			// ***
			// *** Fire the changed event for the access token
			// ***
			this.FireAccessTokenChangedEvent();
		}

		/// <summary>
		/// 
		/// </summary>
		public void FireAccessTokenChangedEvent()
		{
			this.OnAccessTokenChanged(new EventArgs());
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public async Task<bool> RequestAccess(Authorization authorization)
		{
			bool returnValue = false;

			ApiResult response = await Api.Http.Post(Api.KnownUri.RequestAccess(authorization.AuthorizationToken), null);

			if (response.Successful)
			{
				// ***
				// *** Convert this from the json object
				// ***
				this.FromDataObject(response.DataObject);

				// ***
				// *** Fire the changed event for the access token
				// ***
				this.FireAccessTokenChangedEvent();

				// ***
				// *** Return true
				// ***
				returnValue = true;
			}
			else
			{
				// ***
				// *** Reset this object to default values
				// ***
				this.Reset();

				// ***
				// *** Return true
				// ***
				returnValue = false;
			}

			return returnValue;
		}

		protected override void OnFromDataObject(dynamic dataObject)
		{
			if (dataObject.access_token != null)
			{
				string scopeValue = Convert.ToString(dataObject.scope);
				this.AccessToken = new Token() { Identifier = dataObject.access_token };
				this.RefreshToken = new Token() { Identifier = dataObject.refresh_token };
				this.TokenType = dataObject.token_type;
				this.Lifetime = TimeSpan.FromMinutes(((JValue)dataObject.expires_in).ToObject<int>());
				this.ResponseScope = scopeValue.FromApiString();
				this.TimeStamp = DateTime.Now;
			}
			else
			{
				if (dataObject.AccessToken != null)
				{
					this.AccessToken = ((JObject)dataObject.AccessToken).ToObject<Token>();
				}

				if (dataObject.RefreshToken != null)
				{
					this.RefreshToken = ((JObject)dataObject.RefreshToken).ToObject<Token>();
				}

				if (dataObject.TokenType != null)
				{
					this.TokenType = ((JValue)dataObject.TokenType).ToObject<string>();
				}

				if (dataObject.Lifetime != null)
				{
					this.Lifetime = TimeSpan.Parse(((JValue)dataObject.Lifetime).ToObject<string>());
				}

				if (dataObject.ResponseScope != null)
				{
					this.ResponseScope = (AuthorizationScope)((JValue)dataObject.ResponseScope).ToObject<int>();
				}

				if (dataObject.TimeStamp != null)
				{
					this.TimeStamp = DateTime.Parse(((JValue)dataObject.TimeStamp).ToObject<string>());
				}
			}
		}

		protected virtual void OnAccessTokenChanged(EventArgs e)
		{
			if (this.AccessTokenChanged != null)
			{
				this.AccessTokenChanged(this, e);
			}
		}
	}
}
